spb/airiskindex Public
The most methodologically rigorous, fully transparent AI job-exposure index.
TypeScript 88%
Python 6.1%
SQL 2.7%
CSS 1.2%
JavaScript 0.9%
Shell 0.8%
1// File: route.ts2// Path: apps/web/app/api/v1/occupations/[code]/route.ts3// Project: AI Risk Index — airiskindex.io4// Author: Simon-Pierre Boucher5// Contact: contact@spboucher.ai6// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.7//8// Description: Public API: full score breakdown for one occupation.910import { prisma } from "@airiskindex/db";11import { INDEX_VERSION } from "@airiskindex/scoring";12import type { NextRequest } from "next/server";1314export const dynamic = "force-dynamic";1516export async function GET(17 _request: NextRequest,18 { params }: { params: { code: string } },19): Promise<Response> {20 try {21 const occupation = await prisma.occupation.findUnique({22 where: { code: params.code },23 include: {24 tasks: {25 select: { id: true, statement: true, importance: true },26 orderBy: { id: "asc" },27 },28 },29 });30 if (!occupation) {31 return Response.json({ error: "not_found" }, { status: 404 });32 }3334 // Latest run's scores; historical runs stay queryable (CLAUDE.md §9).35 const latest = await prisma.occupationScore.findFirst({36 where: { occupationCode: occupation.code },37 orderBy: { run: { createdAt: "desc" } },38 include: { run: true },39 });4041 return Response.json({42 index_version: latest?.run.indexVersion ?? INDEX_VERSION,43 occupation: {44 code: occupation.code,45 title: occupation.title,46 description: occupation.description,47 esco_uri: occupation.escoUri,48 rome_code: occupation.romeCode,49 median_wage_cents: occupation.medianWageCents,50 wage_currency: occupation.wageCurrency,51 },52 scores: latest53 ? {54 run_id: latest.runId,55 computed_at: latest.run.createdAt,56 substitution: {57 low: latest.substitutionLow,58 score: latest.substitution,59 high: latest.substitutionHigh,60 },61 exposure: {62 low: latest.exposureLow,63 score: latest.exposure,64 high: latest.exposureHigh,65 },66 augmentation: {67 low: latest.augmentationLow,68 score: latest.augmentation,69 high: latest.augmentationHigh,70 },71 highly_exposed_task_share: latest.highlyExposedTaskShare,72 }73 : null,74 tasks: occupation.tasks,75 });76 } catch {77 return Response.json({ error: "database_unavailable" }, { status: 503 });78 }79}80